package server;

import java.net.*;
import java.io.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import DBComm.DBComm;

import parser.AddBuddyMessage;
import parser.ChatMessage;
import parser.MessageParser;
import parser.RemoveBuddyMessage;
import parser.ReturnStatusMessage;

//import Server.DBComm;

public class ServerThread extends Thread
{
    private Socket socket = null;
    private String username = "Client";
    
    PrintWriter out;
    BufferedReader in;
    
    MessageParser messageParser = new MessageParser(); // XML message parser
    
    DBComm dbc = new DBComm();

    public ServerThread(Socket socket)
    {
    	super("ServerThread");
    	this.socket = socket;
    }
    
    private void setUserName(String username) {this.username = username;}
    public String getUserName() {return username;}

    public void run()
    {
		try
		{	
			out = new PrintWriter(socket.getOutputStream(), true); // server socket output
		    in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // client socket input
		    
		    String outputLine = "";
		    String inputLine = "";
		    
		    while((inputLine = in.readLine()) != null) // read lines in from the client input
		    {
		    	System.out.println(username + " sent a message");
		    	
		    	// check input
		    	if(inputLine.equals("Bye"))
		    	{
		    		System.out.println("Client connection terminated");
		    		socket.close();
		    		break;
		    	}
		    	else if(inputLine.startsWith("SETNAME:"))
		    	{
		    		// set the name
		    		inputLine = inputLine.replace("SETNAME:", "");
		    		
		    		System.out.println("\"" + username + "\" has changed his name to \"" + inputLine + "\"");
		    		setUserName(inputLine);
		    	}
		    	else if(inputLine.startsWith("<message>") && inputLine.endsWith("</message>"))
		    	{
		    		// parse out the message
		    		parseMessage(inputLine);
		    		System.out.println(inputLine);
		    	}
		    	else
		    	{
		    		// process input and create an output response
			    	outputLine = inputLine; // echo back what the client sent
			    	out.println("Server: " + outputLine); // send the output back through the output socket
		    	}
		    	
		    }
		    out.close();
		    in.close();
		    //socket.close();
		}
		catch(SocketException e)
		{
			// often thrown when the Client is stopped without typing the "Bye" command
			System.out.println("Client (" + username + ") connection lost: SocketException - " + e.getMessage());
		}
		catch (Exception e) {
		    e.printStackTrace();
		}
    }
    
    private void parseMessage(String inputLine)
    {
    	Document dom;

    	//get the factory
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		
		try {
			
			//Using factory get an instance of document builder
			DocumentBuilder db = dbf.newDocumentBuilder();
			
			//parse using builder to get DOM representation of the XML String
			dom = db.parse(new InputSource(new StringReader(inputLine)));
			
			// parse the document
			parseDocument(dom);
			

		}catch(ParserConfigurationException pce) {
			pce.printStackTrace();
		}catch(SAXException se) {
			se.printStackTrace();
		}catch(IOException ioe) {
			ioe.printStackTrace();
		}
    }
    
    private void parseDocument(Document dom)
    {
    	//get the root element
		Element docEle = dom.getDocumentElement();
		
		// get the "type" tag to figure out the type of message
		String type = messageParser.getTextValue(docEle, "type");
		
		if(type.equals("Chat")) // Chat message
		{
			ChatMessage c = messageParser.getChatMessage(docEle);
			
			// send the message directly to the recipient
			sendMessageDirectlyTo(c.getFrom(), c.getTo(), c.getText());
			dbc.addMessage(c.getFrom(), c.getText(), c.getTo());
			
		}
		else if(type.equals("AddBuddy")) // Chat message
		{
			AddBuddyMessage abm = messageParser.getAddBuddyMessage(docEle);
			
			// send the message directly to the recipient
			sendMessageDirectlyTo(abm.getFrom(), abm.getTo(), "Text: " + abm.getFrom() + " Added Buddy - " + abm.getTo());
			dbc.addBuddy(abm.getFrom(), abm.getTo(), "Buddies");
		}
		else if(type.equals("RemoveBuddy")) // Chat message
		{
			RemoveBuddyMessage rbm = messageParser.getRemoveBuddyMessage(docEle);
			
			// send the message directly to the recipient
			sendMessageDirectlyTo(rbm.getFrom(), rbm.getTo(), "Text: " + rbm.getFrom() + " Removed Buddy - " + rbm.getTo());
			dbc.removeBuddy(rbm.getFrom(), rbm.getTo(), "Buddies");
		}
		else if(type.equals("UpdateStatus")) // Chat message
		{
			ReturnStatusMessage rsm = messageParser.getReturnStatusMessage(docEle);
			
			// send the message directly to the recipient
//			sendMessageDirectlyTo(rsm.getFrom(), rsm.getTo(), "Text: " + rsm.getFrom() + " Removed Buddy - " + rsm.getTo());
			dbc.updateStatus(rsm.getUsername(), rsm.getStatus());
		}
		
    }
    
    // send a message directly through this thread's socket to the connected client
    public void sendMessage(String userFrom, String message)
    {
    	try
    	{
    		out.println(userFrom + ": " + message);
    	}
    	catch(Exception e)
    	{
    		System.out.println("Exception in sendMessage: " + e.getClass().getSimpleName() + " - " + e.getMessage());
    	}
    }
    
    // send a message from a given user (ServerThread) directly to a given user (ServerThread)
    public void sendMessageDirectlyTo(String userFrom, String userTo, String message)
    {
    	// get a count of currently active threads in this ThreadGroup and create
    	// an empty array of the same size as the count
		int activeThreads = this.getThreadGroup().activeCount();
		Thread[] threadList = new Thread[activeThreads];
		
		// copy all currently active threads in this ThreadGroup into the array
		this.getThreadGroup().enumerate(threadList);
		
		ServerThread theClient = this; // get the current client by default
		for(Thread t : threadList) // look through the threads to find ServerThread classes
		{
			if(t instanceof ServerThread) // make sure we're looking at the clients (ServerThread classes)
			{
				ServerThread s = (ServerThread)t;
				
				if(s.getUserName().equals(userTo))
				{
					// found the desired recipient
					theClient = s;
					break;
				}
			}
		}
		
		if(theClient != null)
		{
			// pipe the message out to the client socket
			theClient.sendMessage(userFrom, message);
		}
    }
}

